/*
 * DIM-SUM操作系统 -- 设备树
 *
 * Copyright (C) 2023 国科础石(重庆)软件有限公司
 *
 * 作者: Dong Peng <w-pengdong@kernelsoft.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */
#include <dim-sum/devtree.h>
#include <dim-sum/errno.h>
#include <dim-sum/ioport.h>
#include <dim-sum/printk.h>
#include <dim-sum/string.h>
#include <dim-sum/init.h>
#include <asm/io.h>
#undef pr_debug
#define pr_debug(fmt, ...) 
struct device_node *dt_irq_find_parent(struct device_node *child)
{
	struct device_node *p;
	phandle parent;

	if (!dt_node_hold(child))
		return NULL;

	do {
		if (dt_get_property_and_size(child, "interrupt-parent", &parent)) {
			p = dt_get_parent(child);
		} else	{
			p = dt_find_by_phandle(parent);
		}
		dt_node_loosen(child);
		child = p;
	} while (p && dt_get_property(p, "#interrupt-cells") == NULL);

	return p;
}

int dt_irq_parse_raw(const __be32 *addr, struct dt_phandle_args *out_irq)
{
	struct device_node *ipar, *tnode, *old = NULL, *newpar = NULL;
	__be32 initial_match_array[MAX_PHANDLE_ARGS];
	const __be32 *match_array = initial_match_array;
	const __be32 *tmp, *imap, *imask, dummy_imask[] = { [0 ... MAX_PHANDLE_ARGS] = cpu_to_be32(~0) };
	u32 intsize = 1, addrsize, newintsize = 0, newaddrsize = 0;
	int imaplen, match, i, rc = -EINVAL;


	ipar = dt_node_hold(out_irq->node);

	/* First get the #interrupt-cells property of the current cursor
	 * that tells us how to interpret the passed-in intspec. If there
	 * is none, we are nice and just walk up the tree
	 */
	do {
		if (!dt_read_u32(ipar, "#interrupt-cells", &intsize))
			break;
		tnode = ipar;
		ipar = dt_irq_find_parent(ipar);
		dt_node_loosen(tnode);
	} while (ipar);
	if (ipar == NULL) {
		pr_debug(" -> no parent found !\n");
		goto fail;
	}

	pr_debug("dt_irq_parse_raw: ipar=%pOF, size=%d\n", ipar, intsize);

	if (out_irq->args_count != intsize)
		goto fail;

	/* Look for this #address-cells. We have to implement the old linux
	 * trick of looking for the parent here as some device-trees rely on it
	 */
	old = dt_node_hold(ipar);
	do {
		tmp = dt_get_property_and_size(old, "#address-cells", NULL);
		tnode = dt_get_parent(old);
		dt_node_loosen(old);
		old = tnode;
	} while (old && tmp == NULL);
	dt_node_loosen(old);
	old = NULL;
	addrsize = (tmp == NULL) ? 2 : be32_to_cpu(*tmp);

	pr_debug(" -> addrsize=%d\n", addrsize);

	/* Range check so that the temporary buffer doesn't overflow */
	if (WARN_ON(addrsize + intsize > MAX_PHANDLE_ARGS)) {
		rc = -EFAULT;
		goto fail;
	}

	/* Precalculate the match array - this simplifies match loop */
	for (i = 0; i < addrsize; i++)
		initial_match_array[i] = addr ? addr[i] : 0;
	for (i = 0; i < intsize; i++)
		initial_match_array[addrsize + i] = cpu_to_be32(out_irq->args[i]);

	/* Now start the actual "proper" walk of the interrupt tree */
	while (ipar != NULL) {
		/* Now check if cursor is an interrupt-controller and if it is
		 * then we are done
		 */
		if (dt_read_bool(ipar, "interrupt-controller")) {
			pr_debug(" -> got it !\n");
			return 0;
		}

		/*
		 * interrupt-map parsing does not work without a reg
		 * property when #address-cells != 0
		 */
		if (addrsize && !addr) {
			pr_debug(" -> no reg passed in when needed !\n");
			goto fail;
		}

		/* Now look for an interrupt-map */
		imap = dt_get_property_and_size(ipar, "interrupt-map", &imaplen);
		/* No interrupt map, check for an interrupt parent */
		if (imap == NULL) {
			pr_debug(" -> no map, getting parent\n");
			newpar = dt_irq_find_parent(ipar);
			goto skiplevel;
		}
		imaplen /= sizeof(u32);

		/* Look for a mask */
		imask = dt_get_property_and_size(ipar, "interrupt-map-mask", NULL);
		if (!imask)
			imask = dummy_imask;

		/* Parse interrupt-map */
		match = 0;
		while (imaplen > (addrsize + intsize + 1) && !match) {
			/* Compare specifiers */
			match = 1;
			for (i = 0; i < (addrsize + intsize); i++, imaplen--)
				match &= !((match_array[i] ^ *imap++) & imask[i]);

			pr_debug(" -> match=%d (imaplen=%d)\n", match, imaplen);

			/* Get the interrupt parent */
			newpar = dt_find_by_phandle(be32_to_cpup(imap));
			imap++;
			--imaplen;

			/* Check if not found */
			if (newpar == NULL) {
				pr_debug(" -> imap parent not found !\n");
				goto fail;
			}

			if (!is_device_available(newpar))
				match = 0;

			/* Get #interrupt-cells and #address-cells of new
			 * parent
			 */
			if (dt_read_u32(newpar, "#interrupt-cells",
						 &newintsize)) {
				pr_debug(" -> parent lacks #interrupt-cells!\n");
				goto fail;
			}
			if (dt_read_u32(newpar, "#address-cells",
						 &newaddrsize))
				newaddrsize = 0;

			pr_debug(" -> newintsize=%d, newaddrsize=%d\n",
			    newintsize, newaddrsize);

			/* Check for malformed properties */
			if (WARN_ON(newaddrsize + newintsize > MAX_PHANDLE_ARGS)
			    || (imaplen < (newaddrsize + newintsize))) {
				rc = -EFAULT;
				goto fail;
			}

			imap += newaddrsize + newintsize;
			imaplen -= newaddrsize + newintsize;

			pr_debug(" -> imaplen=%d\n", imaplen);
		}
		if (!match)
			goto fail;

		/*
		 * Successfully parsed an interrrupt-map translation; copy new
		 * interrupt specifier into the out_irq structure
		 */
		match_array = imap - newaddrsize - newintsize;
		for (i = 0; i < newintsize; i++)
			out_irq->args[i] = be32_to_cpup(imap - newintsize + i);
		out_irq->args_count = intsize = newintsize;
		addrsize = newaddrsize;

	skiplevel:
		/* Iterate again with new parent */
		out_irq->node = newpar;
		pr_debug(" -> new parent: %pOF\n", newpar);
		dt_node_loosen(ipar);
		ipar = newpar;
		newpar = NULL;
	}
	rc = -ENOENT; /* No interrupt-map found */

 fail:
	dt_node_loosen(ipar);
	dt_node_loosen(newpar);

	return rc;
}

int dt_irq_parse_one(struct device_node *device, u32 index, struct dt_phandle_args *out_irq)
{
	struct device_node *p;
	const __be32 *addr;
	u32 intsize;
	int i, res;

	pr_debug("dt_irq_parse_one: dev=%pOF, index=%d\n", device, index);

	/* OldWorld mac stuff is "special", handle out of line */

	/* Get the reg property (if any) */
	addr = dt_get_property(device, "reg");

	/* Try the new-style interrupts-extended first */
	res = dt_parse_phandle_with_args(device, "interrupts-extended",
					"#interrupt-cells", index, out_irq);
	if (!res)
		return dt_irq_parse_raw(addr, out_irq);

	/* Look for the interrupt parent. */
	p = dt_irq_find_parent(device);
	if (p == NULL)
		return -EINVAL;

	/* Get size of interrupt specifier */
	if (dt_read_u32(p, "#interrupt-cells", &intsize)) {
		res = -EINVAL;
		goto out;
	}

	pr_debug(" parent=%pOF, intsize=%d\n", p, intsize);

	/* Copy intspec into irq structure */
	out_irq->node = p;
	out_irq->args_count = intsize;
	for (i = 0; i < intsize; i++) {
		res = dt_read_u32_index(device, "interrupts",
						 out_irq->args + i, (index * intsize) + i);
		if (res)
			goto out;
	}

	pr_debug(" intspec=%d\n", *out_irq->args);


	/* Check if there are any interrupt-map translations to process */
	res = dt_irq_parse_raw(addr, out_irq);
 out:
	dt_node_loosen(p);
	return res;
}

u32 dt_irq_count(struct device_node *device)
{
	struct dt_phandle_args irq;
	u32 nr = 0;

	while (dt_irq_parse_one(device, nr, &irq) == 0)
		nr++;

	return nr;
}
